﻿using MongoDB.Bson;
using Newtonsoft.Json.Linq;
namespace anydata.Models
{
    public class ObjectKey
    {
        public string RootKey { get; set; }
        public bool HasRootKey { get; set; }
        public string SelectPath { get; set; }
        public string[] Paths { get; set; }
        public bool HasSelectPath { get; set; }
        public string Error { get; set; }
        public bool HasError { get; set; }
        public ObjectKey Parse(string key)
        {
            HasRootKey = false;
            HasSelectPath = false;
            if (!string.IsNullOrWhiteSpace(key))
            {
                Paths = key.Split('.');
                if (Paths.Length > 0 && !string.IsNullOrWhiteSpace(Paths[0]))
                {
                    HasRootKey = true;
                    RootKey = Paths[0];
                    if (Paths.Length > 1 && !string.IsNullOrWhiteSpace(Paths[1]))
                    {
                        HasSelectPath = true;
                        Paths = Paths.Skip(1).Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
                        SelectPath = string.Join('.', Paths);
                    }
                }
                else
                {
                    HasError = true;
                    Error = "根属性不能为空!";
                }
            }
            return this;
        }
        public JObject Create(JToken data)
        {
            if (!HasSelectPath)
            {
                return (JObject)data;
            }
            JObject root = new JObject();
            JObject current = root;
            for (var i = 0; i < Paths.Length; i++)
            {
                if ((i + 1) == Paths.Length)
                {
                    current.Add(Paths[i], data);
                }
                else
                {
                    var node = new JObject();
                    current.Add(Paths[i], node);
                    current = node;
                }
            }
            return root;
        }
        public JObject Delete(JObject data)
        {
            if (data != default && data.HasValues)
            {
                if (HasSelectPath)
                {
                    var node = data.SelectToken(SelectPath);
                    if (node != default)
                    {
                        node?.Parent?.Remove();
                    }
                }
                else
                {
                    return new JObject();
                }
            }
            return data ?? new JObject();
        }
        public BsonDocument Root()
        {
            BsonDocument bson = new BsonDocument();
            if (HasSelectPath)
            {
                bson.Add("newRoot", $"${SelectPath}");
            }
            else
            {
                bson.Add("newRoot", $"$$ROOT");
            }
            return bson;
        }
        public bool Operate(JObject data, 
            ObjectSetData objectSet, 
            out JObject newData)
        {
            newData = Create(objectSet.GetData());
            if (data != default)
            {
                if (!HasSelectPath && JToken.DeepEquals(newData, data))
                {
                    return false;
                }
                if (HasSelectPath && JToken.DeepEquals(newData.SelectToken(SelectPath), data.SelectToken(SelectPath)))
                {
                    return false;
                }
                var merge = JsonSettings.MergeUnion;
                switch (objectSet.Operation)
                {
                    case "replaceAll":
                        data = Delete(data)??new JObject();
                        goto default;
                    case "replace":
                        merge = JsonSettings.MergeReplace;
                        goto default;
                    case "append":
                        merge = JsonSettings.MergeConcat;
                        goto default;
                    case "update":
                        var local = string.IsNullOrEmpty(SelectPath) ? data : data.SelectToken(SelectPath);
                        var update = objectSet.GetData();
                        if (local != default && local.Type == JTokenType.Array)
                        {
                            var ArrayNode = (JArray)local;
                            switch (update.Type)
                            {
                                case JTokenType.Array:
                                    foreach (JToken item in update)
                                    {
                                        if(item.Type == JTokenType.Object)
                                        {
                                            if (!Update(ArrayNode, (JObject)item))
                                            {
                                                ArrayNode.Add(item);
                                            }
                                        }
                                    }
                                    newData = data;
                                    return true;
                                case JTokenType.Object:
                                    if (!Update(ArrayNode, (JObject)update))
                                    {
                                        ArrayNode.Add(update);
                                    }
                                    newData = data;
                                    return true;
                            }
                        }
                        goto default;
                    default:
                        data.Merge(newData, merge);
                        newData = data;
                        break;
                }
            }
            return true;
        }
        private bool Update(JArray ArrayNode, JObject update)
        {
            var updated = false;
            for (var i = 0; i < ArrayNode.Count(); i++)
            {
                var replace = true;
                var lobject = (JObject)ArrayNode[i];
                foreach (var item in update.Properties())
                {
                    if (!lobject.ContainsKey(item.Name))
                    {
                        replace = false;
                        break;
                    }
                }
                if (replace)
                {
                    updated = true;
                    ArrayNode[i] = update;
                }
            }
            return updated;
        }
        public BsonDocument Match(BsonDocument filter)
        {
            var bson = new BsonDocument();
            for(var i = 0; i < filter.Count(); i++)
            {
                var item = filter.GetElement(i);
                var name = item.Name;
                var value = item.Value;
                if (value.IsBsonDocument)
                {
                    value = Match(value.AsBsonDocument);
                }
                if (!item.Name.Contains("$"))
                {
                    name = $"{SelectPath}.{item.Name}";
                }
                bson.Add(name, value);
            }
            return bson;
        }
    }
}
